package org.xx.armory.commons;

import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import static java.util.Arrays.stream;
import static java.util.Collections.emptyList;
import static org.xx.armory.commons.Validators.rejectIfNull;

/**
 * 类型转换器，用于执行相容的转换，如果无法完成则抛出异常。
 *
 * <p>
 * 和 {@link StringConverter}不同，此类型不会针对字符串进行格式解析，并且在转化失败时抛出异常。
 * </p>
 *
 * @see StringConverter
 */
public final class Converter {
    private Converter() {
        throw new AssertionError();
    }

    /**
     * 将指定的值转化为字符串。
     * <p>
     * 如果待转化的值是 {@code null}，那么返回空字符串。
     * </p>
     * <p>
     * 否则调用 {@link Object#toString()}方法转化字符串。
     * </p>
     *
     * @param object
     *         待转化的值。
     * @return 转化结果。
     */
    public static String toStr(
            Object object
    ) {
        return object != null ?
                object instanceof StringEnum ? ((StringEnum) object).stringValue() :
                        object instanceof IntegerEnum ? String.valueOf(((IntegerEnum) object).value()) :
                                object.toString() : "";
    }

    /**
     * 格式化转换异常信息。
     *
     * @param object
     *         待转换的原对象。
     * @param type
     *         转换的目标类型。
     * @return 转换异常信息。
     */
    private static String formatConvertException(
            Object object,
            Type type
    ) {
        if (object == null) {
            return "Cannot convert null to " + type.getTypeName();
        } else {
            return "Cannot convert " + object.getClass().getTypeName() + " to " + type.getTypeName();
        }
    }

    /**
     * 将指定的值转化为整数。
     * <p>
     * 如果待转化的值是{@link Number}，那么返回该值的字节形式。
     * </p>
     * <p>
     * 否则抛出异常。
     * </p>
     *
     * @param object
     *         待转化的值。
     * @return 转化结果。
     * @throws IllegalArgumentException
     *         如果参数{@code object}不是相容的类型，或者转化失败。
     */
    public static byte toByte(
            Object object
    ) {
        if (object instanceof Number) {
            return ((Number) object).byteValue();
        } else if (object instanceof Boolean) {
            return (Boolean) object ? (byte) 1 : (byte) 0;
        } else {
            throw new IllegalArgumentException(formatConvertException(object, byte.class));
        }
    }

    /**
     * 将指定的值转化为整数。
     * <p>
     * 如果待转化的值是{@link Number}，那么返回该值的整数形式。
     * </p>
     * <p>
     * 否则抛出异常。
     * </p>
     *
     * @param object
     *         待转化的值。
     * @return 转化结果。
     * @throws IllegalArgumentException
     *         如果参数{@code object}不是相容的类型，或者转化失败。
     */
    public static short toShort(
            Object object
    ) {
        if (object instanceof Number) {
            return ((Number) object).shortValue();
        } else if (object instanceof Boolean) {
            return (Boolean) object ? (short) 1 : (short) 0;
        } else {
            throw new IllegalArgumentException(formatConvertException(object, short.class));
        }
    }

    /**
     * 将指定的值转化为整数。
     * <p>
     * 如果待转化的值是{@link Number}，那么返回该值的整数形式。
     * </p>
     * <p>
     * 否则抛出异常。
     * </p>
     *
     * @param object
     *         待转化的值。
     * @return 转化结果。
     * @throws IllegalArgumentException
     *         如果参数{@code object}不是相容的类型，或者转化失败。
     */
    public static int toInt(
            Object object
    ) {
        if (object instanceof Number) {
            return ((Number) object).intValue();
        } else if (object instanceof Boolean) {
            return (Boolean) object ? 1 : 0;
        } else {
            throw new IllegalArgumentException(formatConvertException(object, int.class));
        }
    }

    public static byte[] toByteArray(
            int... src
    ) {
        if (src == null || src.length == 0) {
            return new byte[0];
        }

        final var result = new byte[src.length];

        for (var i = 0; i < src.length; ++i) {
            result[i] = (byte) src[i];
        }

        return result;
    }

    public static byte[] toByteArray(
            Object... objects
    ) {
        if (objects == null || objects.length == 0) {
            return new byte[0];
        } else {
            return toByteArray(toIntArray(objects));
        }
    }

    public static int[] toIntArray(
            Object... objects
    ) {
        if (objects == null || objects.length == 0) {
            return new int[0];
        } else {
            return stream(objects).filter(Objects::nonNull).mapToInt(Converter::toInt).toArray();
        }
    }

    public static long[] toLongArray(
            Object... objects
    ) {
        if (objects == null || objects.length == 0) {
            return new long[0];
        } else {
            return stream(objects).filter(Objects::nonNull).mapToLong(Converter::toLong).toArray();
        }
    }

    public static double[] toDoubleArray(
            Object... objects
    ) {
        if (objects == null || objects.length == 0) {
            return new double[0];
        } else {
            return stream(objects).filter(Objects::nonNull).mapToDouble(Converter::toDouble).toArray();
        }
    }

    public static String[] toStringArray(
            Object... objects
    ) {
        if (objects == null || objects.length == 0) {
            return new String[0];
        } else {
            return stream(objects).filter(Objects::nonNull).map(Converter::toStr).toArray(String[]::new);
        }
    }

    public static List<Integer> toIntList(
            Object... objects
    ) {
        if (objects == null || objects.length == 0) {
            return emptyList();
        } else {
            return stream(objects).filter(Objects::nonNull).map(Converter::toInt).collect(Collectors.toList());
        }
    }

    public static List<Long> toLongList(
            Object... objects
    ) {
        if (objects == null || objects.length == 0) {
            return emptyList();
        } else {
            return stream(objects).filter(Objects::nonNull).map(Converter::toLong).collect(Collectors.toList());
        }
    }

    public static List<Double> toDoubleList(
            Object... objects
    ) {
        if (objects == null || objects.length == 0) {
            return emptyList();
        } else {
            return stream(objects).filter(Objects::nonNull).map(Converter::toDouble).collect(Collectors.toList());
        }
    }

    public static List<String> toStringList(
            Object... objects
    ) {
        if (objects == null || objects.length == 0) {
            return emptyList();
        } else {
            return stream(objects).filter(Objects::nonNull).map(Converter::toStr).collect(Collectors.toList());
        }
    }

    /**
     * 将指定的值转化为长整数。
     * <p>
     * 如果待转化的值是{@link Number}，那么返回该值的长整数形式。
     * </p>
     * <p>
     * 否则抛出异常。
     * </p>
     *
     * @param object
     *         待转化的值。
     * @return 转化结果。
     * @throws IllegalArgumentException
     *         如果参数{@code object}不是相容的类型，或者转化失败。
     */
    public static long toLong(Object object) {
        if (object instanceof Number) {
            return ((Number) object).longValue();
        } else if (object instanceof Boolean) {
            return (Boolean) object ? 1L : 0L;
        } else {
            throw new IllegalArgumentException(formatConvertException(object, long.class));
        }
    }

    /**
     * 将指定的值转化为双精度数。
     * <p>
     * 如果待转化的值是{@link Number}，那么返回该值的双精度数形式。
     * </p>
     * <p>
     * 否则抛出异常。
     * </p>
     *
     * @param object
     *         待转化的值。
     * @return 转化结果。
     * @throws IllegalArgumentException
     *         如果参数{@code object}不是相容的类型，或者转化失败。
     */
    public static double toDouble(Object object) {
        if (object instanceof Number) {
            return ((Number) object).doubleValue();
        } else if (object instanceof Boolean) {
            return (Boolean) object ? 1D : 0D;
        } else {
            throw new IllegalArgumentException(formatConvertException(object, double.class));
        }
    }

    /**
     * 将指定的值转化为十进制数。
     * <p>
     * 如果待转化的值是{@link Number}，那么先将该值转化为双精度数，然后通过此双精度数构造十进制数并返回。
     * </p>
     * <p>
     * 否则抛出异常。
     * </p>
     *
     * @param object
     *         待转化的值。
     * @return 转化结果。
     * @throws IllegalArgumentException
     *         如果参数{@code object}不是相容的类型，或者转化失败。
     * @see BigDecimal#valueOf(double)
     */
    public static BigDecimal toBigDecimal(Object object) {
        if (object instanceof BigDecimal) {
            return (BigDecimal) object;
        } else if (object instanceof BigInteger) {
            return new BigDecimal((BigInteger) object);
        } else if (object instanceof Double) {
            return BigDecimal.valueOf((Double) object);
        } else if (object instanceof Float) {
            return BigDecimal.valueOf((Float) object);
        } else if (object instanceof Number) {
            return BigDecimal.valueOf(((Number) object).longValue());
        } else if (object instanceof Boolean) {
            return (Boolean) object ? BigDecimal.ONE : BigDecimal.ZERO;
        } else if (object == null) {
            return null;
        } else {
            throw new IllegalArgumentException(formatConvertException(object, BigDecimal.class));
        }
    }

    /**
     * 将指定的值转化为日期。
     * <p>
     * 如果待转化的值是{@link Date}，那么直接返回该值。
     * </p>
     * <p>
     * 如果待转化的值是{@link Number}，那么将该数字作为时间戳，通过 {@link Date#Date(long)}方法构造日期。
     * </p>
     * <p>
     * 否则抛出异常。
     * </p>
     *
     * @param object
     *         待转化的值。
     * @return 转化结果。
     * @throws IllegalArgumentException
     *         如果参数{@code object}不是相容的类型，或者转化失败。
     */
    public static Date toDate(Object object) {
        if (object instanceof Date) {
            return new Date(((Date) object).getTime());
        } else if (object instanceof Number) {
            return new Date(((Number) object).longValue());
        } else if (object == null) {
            return null;
        } else {
            throw new IllegalArgumentException(formatConvertException(object, Date.class));
        }
    }

    /**
     * 将指定的值转为枚举。
     *
     * @param object
     *         待转化的值。
     * @param clazz
     *         目标枚举类型。
     * @param <T>
     *         目标枚举类型。
     * @return 转化结果。
     * @throws IllegalArgumentException
     *         如果参数{@code object}不是相容的类型，或者转化失败。
     */
    public static <T extends Enum<T> & IntegerEnum> T toIntegerEnum(
            Object object,
            Class<T> clazz
    ) {
        rejectIfNull(clazz, "clazz");

        if (clazz.isInstance(object)) {
            return clazz.cast(object);
        } else if (object == null) {
            return null;
        } else if (object instanceof Number) {
            final var iv = ((Number) object).intValue();
            for (final var ev : clazz.getEnumConstants()) {
                if (ev.value() == iv) {
                    return ev;
                }
            }
            throw new IllegalArgumentException("Cannot convert number " + iv + " to " + clazz.getTypeName());
        } else {
            throw new IllegalArgumentException(formatConvertException(object, clazz));
        }
    }

    /**
     * 将指定的值转为枚举。
     *
     * @param object
     *         待转化的值。
     * @param clazz
     *         目标枚举类型。
     * @param <T>
     *         目标枚举类型。
     * @return 转化结果。
     * @throws IllegalArgumentException
     *         如果参数{@code object}不是相容的类型，或者转化失败。
     */
    public static <T extends Enum<T> & StringEnum> T toStringEnum(
            Object object,
            Class<T> clazz
    ) {
        rejectIfNull(clazz, "clazz");

        if (clazz.isInstance(object)) {
            return clazz.cast(object);
        } else if (object == null) {
            return null;
        } else if (object instanceof CharSequence) {
            final var sv = object.toString();
            if (sv.isEmpty()) {
                return null;
            }
            for (final var ev : clazz.getEnumConstants()) {
                if (ev.stringValue().equalsIgnoreCase(sv)) {
                    return ev;
                }
            }
            throw new IllegalArgumentException("Cannot convert number " + sv + " to " + clazz.getTypeName());
        } else {
            throw new IllegalArgumentException(formatConvertException(object, clazz));
        }
    }

    /**
     * 将指定的整数转为枚举集合。
     * <p>待转化的整数可以是若干枚举进行按位或的结果。</p>
     *
     * @param n
     *         待转化的整数。
     * @param clazz
     *         目标枚举类型。
     * @param <T>
     *         目标枚举类型。
     * @return 转化结果。
     * @throws IllegalArgumentException
     *         如果参数{@code object}不是相容的类型，或者转化失败。
     * @see #toInt(Set)
     */
    public static <T extends Enum<T> & IntegerEnum> Set<T> toIntegerEnumSet(
            int n,
            Class<T> clazz
    ) {
        rejectIfNull(clazz, "clazz");

        if (!clazz.isAnnotationPresent(Flags.class)) {
            throw new IllegalArgumentException(
                    "Class " + clazz.getTypeName() + " must has annotation " + Flags.class.getTypeName());
        }

        final var result = new HashSet<T>();
        for (final var ev : clazz.getEnumConstants()) {
            if ((ev.value() & n) != 0) {
                result.add(ev);
            }
        }
        return result;
    }

    /**
     * 将指定的整数集合转为枚举集合。
     *
     * @param a
     *         待转化的整数数组。
     * @param clazz
     *         目标枚举类型。
     * @param <T>
     *         目标枚举类型。
     * @return 转化结果。
     * @throws IllegalArgumentException
     *         如果参数{@code object}不是相容的类型，或者转化失败。
     * @see #toInt(Set)
     */
    public static <T extends Enum<T> & IntegerEnum> Set<T> toIntegerEnumSet(
            int[] a,
            Class<T> clazz
    ) {
        rejectIfNull(a, "a");
        rejectIfNull(clazz, "clazz");

        final var result = new HashSet<T>();
        for (final var n : a) {
            for (final var ev : clazz.getEnumConstants()) {
                if (ev.value() == n) {
                    result.add(ev);
                }
            }
        }
        return result;
    }

    /**
     * 将指定的字符串集合转为枚举集合。
     *
     * @param a
     *         待转化的字符串数组。
     * @param clazz
     *         目标枚举类型。
     * @param <T>
     *         目标枚举类型。
     * @return 转化结果。
     * @throws IllegalArgumentException
     *         如果参数{@code object}不是相容的类型，或者转化失败。
     * @see #toInt(Set)
     */
    public static <T extends Enum<T> & StringEnum> Set<T> toStringEnumSet(
            String[] a,
            Class<T> clazz
    ) {
        rejectIfNull(a, "a");
        rejectIfNull(clazz, "clazz");

        final var result = new HashSet<T>();
        for (final var sv : a) {
            for (final var ev : clazz.getEnumConstants()) {
                if (ev.stringValue().equalsIgnoreCase(sv)) {
                    result.add(ev);
                }
            }
        }
        return result;
    }

    /**
     * 将枚举集合按位或的方式生成整数，如果参数{@code set}是空集合则返回{@code 0}。
     *
     * @param set
     *         包含枚举的集合。
     * @param <T>
     *         枚举类型。
     * @return 位或的执行结果。
     * @see #toIntegerEnumSet(int, Class)
     */
    public static <T extends Enum<T> & IntegerEnum> int toInt(
            Set<T> set
    ) {
        if (set == null || set.isEmpty()) {
            return 0;
        }

        var result = 0;
        for (final var ev : set) {
            result |= ev.value();
        }
        return result;
    }

    /**
     * 将长整数通过截断的方式转化为整数。
     *
     * @param l
     *         待截断的长整数。
     * @return 截断的结果。
     */
    public static int truncateToInteger(long l) {
        if (l > Integer.MAX_VALUE) {
            return Integer.MAX_VALUE;
        } else if (l < Integer.MIN_VALUE) {
            return Integer.MIN_VALUE;
        } else {
            return (int) (0x0FFFFFFFFL & l);
        }
    }

    /**
     * 尝试将对象转化为指定的类型。
     *
     * @param <T>
     *         目标类型。
     * @param t
     *         待转化的对象。
     * @param clazz
     *         目标类型。
     * @return 转化的结果，如果参数{@code t}是{@code null}那么返回{@code null}，如果参数{@code t}可以被强制转化为{@code clazz}
     * ，那么返回{@code t}本身，否则也返回{@code null}。
     */
    public static <T> T tryCast(
            Object t,
            Class<T> clazz
    ) {
        rejectIfNull(clazz, "clazz");

        if (t != null) {
            if (clazz.isAssignableFrom(t.getClass())) {
                return clazz.cast(t);
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    public static byte[] toBytes(
            long n
    ) {
        final var result = new byte[8];
        for (int i = 0; i < 8; ++i) {
            result[i] = (byte) (n & 0xff);
            n >>>= 8;
        }
        return result;
    }

    public static byte[] toBytes(
            int n
    ) {
        final var result = new byte[4];
        for (int i = 0; i < 4; ++i) {
            result[i] = (byte) (n & 0xff);
            n >>>= 8;
        }
        return result;
    }
}
